package schuchert.contest;

import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import com.ibm.contest.instrumentation.Instrument;

public class ConTestInstrumentationMainBasedClassFileTransformer implements ClassFileTransformer {
    public static final String CON_TEST_INSTRUMENTATION_CLASS_FILTER = "ConTestInstrumentation.classFilter";
    public static final String CON_TEST_INSTRUMENTATION_DELETE_FILES = "ConTestInstrumentation.deleteFiles";
    public static final String CON_TEST_INSTRUMENTATION_TEMP_DIR = "ConTestInstrumentation.tempDir";
    String tempDirName = "TEMP_DIR";
    File tempDirectory;

    boolean deleteFilesAfterTransformation = true;
    private List<String> filterPatterns = Collections.emptyList();

    public ConTestInstrumentationMainBasedClassFileTransformer() {
        processProperties();
        setupTempDirectory();
    }

    private void setupTempDirectory() {
        try {
            tempDirectory = FileUtilities.createTemporaryDirectory(tempDirName);
            if (deleteFilesAfterTransformation)
                tempDirectory.deleteOnExit();
        } catch (Exception e) {
            e.printStackTrace(System.err);
            throw new RuntimeException(e);
        }
    }

    private void processProperties() {
        String property = System.getProperty(CON_TEST_INSTRUMENTATION_DELETE_FILES);
        if (property != null)
            deleteFilesAfterTransformation = Boolean.parseBoolean(property);

        property = System.getProperty(CON_TEST_INSTRUMENTATION_TEMP_DIR);
        if (property != null)
            tempDirName = property;

        property = System.getProperty(CON_TEST_INSTRUMENTATION_CLASS_FILTER);
        if (property != null)
            setFilter(property);
    }

    File getTempDirectory() {
        return tempDirectory;
    }

    public byte[] transform(ClassLoader loader, String fullyQualifiedClassName,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
            throws IllegalClassFormatException {

        if (isClassWeShouldProcess(fullyQualifiedClassName)) {
            try {
                File originalBytesFile = FileUtilities.writeClassFile(getTempDirectory(),
                        fullyQualifiedClassName, classfileBuffer);
                Instrument.main(new String[] { originalBytesFile.getAbsolutePath() });
                byte[] instrumentedBytes = FileUtilities.readFileFully(originalBytesFile);
                cleanupTemporaryFiles(originalBytesFile);
                return instrumentedBytes;
            } catch (Exception e) {
                e.printStackTrace(System.err);
            }
        }

        return null;
    }

    private void cleanupTemporaryFiles(File originalBytesFile) {
        if (deleteFilesAfterTransformation) {
            FileUtilities.removeFile(originalBytesFile);
            File generatedBackupFile = new File(originalBytesFile.getAbsoluteFile() + "_backup");
            FileUtilities.removeFile(generatedBackupFile);
        }
    }

    boolean isClassWeShouldProcess(String fullyQualifiedClassname) {
        boolean noMatchingPatternResponse = true;

        for (String pattern : filterPatterns) {
            char operation = pattern.charAt(0);
            pattern = pattern.substring(1);
            switch (operation) {
            case '+':
                noMatchingPatternResponse = false;
                if (fullyQualifiedClassname.matches(pattern))
                    return true;
                break;
            case '-':
                if (fullyQualifiedClassname.matches(pattern))
                    return false;
                break;
            default:
                System.err.printf(
                        "Invalid pattern: %s for selecting classes to/to not instrument.", pattern);
            }
        }

        return noMatchingPatternResponse;
    }

    public void setFilter(String string) {
        String[] splitPatterns = null;

        if (string == null || string.trim().length() == 0)
            splitPatterns = new String[] {};
        else
            splitPatterns = string.split("::");

        filterPatterns = new LinkedList<String>();
        for (String currentString : splitPatterns) {
            if (currentString == null || currentString.trim().length() == 0)
                continue;
            filterPatterns.add(currentString.trim());
        }

    }
}
